# Nativas Pythonimport sysimport os# Dados Tabularesimport pandas as pdimport numpy as np# Visualizaçãoimport plotly.graph_objects as goimport plotly.express as pximport plotly.figure_factory as ffimport plotly.io as piofrom graphmodex import plotlymodeximport matplotlib.pyplot as pltimport seaborn as sns
Show Code
# Imagem png ou interativa (notebook)pio.renderers.default ='notebook'
Objetivo
Introduzir à ciência de dados e processos de otimização por álgebra linear.
Conceitos de programação:
Arrays
Ajustes de Curva
Otimização
Redes neurais
Bibliotecas:
numpy
plotly
Aplicações:
Ajustar uma reta em dados experimentais (ex.: Conversão do Reator vs Temperatura).
Mostrar coeficientes da regressão (slope/intercept) e interpretar fisicamente.
Métricas de Goodness-of-Fit
Introdução à como funcionam redes neurais
Introdução
O que é modelagem → ajustar uma função matemática a dados experimentais.
Por que é útil → prever comportamento de processos químicos (ex.: reatores, trocadores, cinética).
Regressão linear é o caso mais simples de aprendizado de máquina.
Exemplo físico para motivar: > “Queremos ver como a conversão do reator (X) varia com a temperatura (T) e encontrar a equação linear que melhor representa esse comportamento experimental.”
T (x) → variável independente
X (y) → variável dependente
y = a·x + b
Show Code
# Dados sintéticos de temperatura (K) e conversão (%)T = np.array([300, 320, 340, 360, 380, 400])X_exp = np.array([0.10, 0.20, 0.32, 0.40, 0.48, 0.59]) # dados experimentais
X = np.array(iris.petal_width.copy(deep=True))y = np.array(iris.petal_length.copy(deep=True))
Regressão Linear Matricial
O modelo de regressão linear assume uma das formas mais simples de modelagem, pois conta com os mesmo parâmetros da função linear – ou seja, um coeficiente linear 0 e outros coeficientes angulares n – sendo que é possível alocar múltiplas variáveis de input xn. Sendo ŷ o valor predito, podemos escrever o modelo da seguinte forma:
Nesse caso, representa o vetor de parâmetros do modelo, incluindo o termo de bias 0 e os de features n. O termo x é um vetor que contém os valores de x0 =1 até xn. Portanto, a operação considera o produto matricial entre:
É importante identificar a notação matemática, com o sobrescrito T sendo um indicador de matriz transposta. Isso é necessário pois em ML os vetores são todos representados por vetores coluna. Na regressão linear, precisamos achar o valor de que minimiza o RMSE. Na prática, é mais simples minimizar o MSE, sendo que ele leva para o mesmo resultado já que minimizá-lo também é minimizar sua raiz quadrada. Se considerarmos h como sendo a função de predição do sistema – também chamado de hipótese, indicamos que a hipótese de regressão linear considerando uma parametrização por (h) em um conjunto de dados de treino X, temos a seguinte equação:
Para o caso da regressão linear, o vetor de parâmetros que minimiza MSE() possui uma solução fechada e analítica que é chamada de equação normal. Podemos transformar essa função de minimização em uma forma matricial:
Se XTX for inversível (significa dizer que X -1X=XX -1=I) e, com isso, que det(XTX)0 e que todas as colunas são linearmente independentes, então a solução fechada é dada por:
Aqui, é o valor que minimiza e y é o vetor contendo os valores de y(1) até y(m). Só para ter plena ciência da notação, o vetor x(1) é uma coluna contendo os inputs e o vetor y(1) contém os output. Um exemplo seria dizer que
De forma que y (1) =h(x(1)) é a previsão do valor. É importante entender que há uma forte necessidade de haver inversibilidade em XTX. Mas ainda assim é possível realizar regressões lineares em matrizes não inversíveis a partir do conceito de matriz pseudoinversa. A pseudoinversa de Moore-Penrose (X+) é uma generalização da inversão de matrizes. Quando X for quadrada e inversível, então X+ =X-1. Contudo, em casos em que esses critérios não forem adequados, então podemos usar a decomposição de valores singulares (SVD) de forma que
\[\mathbf{X} = \mathbf{U\Sigma V}^\top\]
\[\mathbf{X}^+ = \mathbf{V\Sigma^+U }^\top\]
Em que U e V são matrizes ortogonais (rotação / espelhamento) e é uma matriz com valores singulares maiores ou iguais a zero. Nesse caso, assim como no próprio numpy, temos que a regressão linear é fornecida por:
\[\hat{\theta} = \mathbf{X^+ y}\]
Como a equação normal computa o inverso de XTX – que é uma matriz de (n+1)(n+1) com n sendo o número de features – a complexidade computacional da inversão dessa matriz reside entre O(n2,4) à O(n3) a depender da implementação. Usando o scikit-learn, a complexidade computacional a partir do SVD é de O(n2), de forma que se dobrarmos a quantidade de features o tempo computacional irá quadruplicar.
Show Code
novo_X = []for value in X: novo_X.append([float(value), 1.])novo_X = np.array(novo_X)print('shape =', novo_X.shape)novo_X[:5]
Podemos adicionar parâmetros com diferentes níveis de exponenciação para caracterizar curvas polinomiais e que, consequentemente, não se adequam ao padrão linear
Aqui, a única diferença da linear é, portanto, a presença de valores exponenciais de X:
\[x \;\;\to\;\; [1, x, x^2, x^3, \dots, x^d]\]
Dessa forma, o modelo polinomial pode ser definido por:
As formas de descoberta dos valores dos parâmetros são as mesmas da Regressão Linear, ou seja, equação normal, decomposição SVD e SGD. Em caso de polinômios de alto grau, é comum a presença de overfitting, de forma que podemos usar mecanismos de regularização (Ridge e Lasso) para evitar esse problema.
def mae(y_pred, y_true):""" Calcula o erro absoluto médio (Mean Absolute Error). Mede a média das diferenças absolutas entre os valores previstos e os reais. É uma métrica simples que indica o quanto, em média, as previsões se afastam dos valores observados. """return np.mean(np.abs(y_pred - y_true)).round(3)def mse(y_pred, y_true):""" Calcula o erro quadrático médio (Mean Squared Error). Mede a média dos quadrados das diferenças entre os valores previstos e os reais. Penaliza mais fortemente grandes erros e é muito usada em problemas de regressão. """return np.mean((y_pred - y_true) **2).round(3)def rmse(y_pred, y_true):""" Calcula a raiz do erro quadrático médio (Root Mean Squared Error). É a raiz quadrada do MSE e possui a mesma unidade dos valores previstos. Fornece uma noção mais intuitiva da magnitude média do erro. """return np.sqrt(mse(y_pred, y_true)).round(3)
Nosso objetivo é minimizar \(\text{MSE}(\theta)\).
3. Gradiente da Função de Custo
O gradiente (vetor de derivadas parciais) indica a direção de maior crescimento de \(\text{MSE}(\theta)\). Para minimizar a função, precisamos mover \(\theta\) na direção oposta ao gradiente:
Atualiza \(\theta\) na direção contrária ao gradiente: \[
\theta := \theta - \eta g_i
\]
Após várias épocas, \(\theta\) converge para valores que minimizam o erro médio.
6. Intuição Geométrica
O vetor \(\theta\) define a reta (ou hiperplano) que ajusta os dados.
O gradiente indica para onde o erro cresce mais.
Atualizar \(\theta\) na direção oposta reduz o erro.
No SGD, como cada passo é feito com apenas um exemplo, o caminho até o mínimo é oscilante, mas converge em média para a solução ótima.
Vamos começar selecionando os mesmos dados do dataset iris que estavmos usando! Aqui, vamos fazer nosso algoritmo já considerando as dimensões certas.
Show Code
X = np.array(iris.petal_width.copy(deep=True))y = np.array(iris.petal_length.copy(deep=True))# Cria matriz de entrada com a forma [[x, 1.], [x, 1.], ...]X_b = np.c_[X, np.ones(len(X))] # shape (6, 2)print(f'{" X_b ":=^11}')print(X_b[:5])print()print(f'{" y ":=^11}')print(y.reshape(-1, 1)[:5])
O parâmetro \(\eta\) é de extrema importância para nosso GoF (goodness-of-fit). Ele indicará se o mínimo local estará muito longe para ser alcançado no nosso número de épocas, se será ideal ou se a gente baterá loucamente em todos os lados da funçaõ convexa.
Show Code
# Hiperparâmetroseta =0.001# learning raten_epochs =100m =len(X)# Inicialização aleatória de theta (2x1)theta = np.random.randn(2, 1)# SGDfor epoch inrange(n_epochs):for i inrange(m): random_index = np.random.randint(m) xi = X_b[random_index:random_index+1] # shape (1, 2) yi = y[random_index:random_index+1].reshape(-1, 1)# Gradiente (forma vetorizada) gradients =2* xi.T.dot(xi.dot(theta) - yi)# Atualização dos parâmetros theta = theta - eta * gradientsprint("Parâmetros ajustados (theta):")print(theta)# Previsõesy_pred = X_b.dot(theta)print("\nPrevisões:")print(y_pred.ravel()[:5])